package org.acompanhamento.domain;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jmine.tec.environment.db.DBEnvironmentHolder;
import jmine.tec.persist.hibernate.traverser.EntityTraverser;
import jmine.tec.persist.hibernate.traverser.TraversingStep;
import jmine.tec.persist.hibernate.traverser.VisitedProperty;
import jmine.tec.test.testcase.BOBaseTestCase;

import org.acompanhamento.AgileTeamCoreTestSuite;
import org.hibernate.type.Type;
import org.springframework.transaction.TransactionStatus;

import bancosys.tec.persist.Persistable;
import bancosys.tec.persist.bussobj.PersistableBusinessObject;
import bancosys.tec.persist.dao.BeanNotFoundException;
import bancosys.tec.utils.date.Date;
import bancosys.tec.utils.reflection.ReflectionUtils;

/**
 * 
 * @author lundberg
 *
 * @param <T>
 */
public abstract class ATBOBaseTestCase<T extends PersistableBusinessObject> extends BOBaseTestCase<T> {

	private static Map<Class<?>, List<ValueForStrategy>> valueForStrategies = new HashMap<>();

	public ATBOBaseTestCase() {
		super();
	}

	public ATBOBaseTestCase(String name) {
		super(name);
	}

	@Override
	protected void initializeTestData() throws BeanNotFoundException {
		super.initializeTestData();
		valueForStrategies().add(new ValueForString());
		valueForStrategies().add(new ValueForDate());
		valueForStrategies().add(new ValueForInteger());
		valueForStrategies().add(new ValueForLong());
		valueForStrategies().add(new ValueForEntity());
	}

	public void addValuesForType(final List<?> values) {
		valueForStrategies().add(0, new ValueForStrategy() {
			@Override
			public boolean matches(int idx, String name, Class<?> type) {
				return type.isInstance(values.get(idx));
			}
			@Override
			public Object valueFor(int idx, String name, Class<?> type) {
				return values.get(idx);
			}
		});
	}

	@Override
	protected void compareData(final int idx, T bean) throws BeanNotFoundException {
		getBean("entityTraverser", EntityTraverser.class).traverse(bean, new BaseEntityVisitor<T>() {
			@Override
			public TraversingStep visitProperty(
					VisitedProperty<? extends Type> property) {
				assertThat(property.getValue(), is(equalTo(valueFor(idx, property.getName(), property.getType().getReturnedClass()))));
				return TraversingStep.NEXT;
			}
		});
	}

	@Override
	protected void fillData(final int idx, T bean) {
		getBean("entityTraverser", EntityTraverser.class).traverse(bean, new BaseEntityVisitor<T>() {
			@Override
			public TraversingStep visitProperty(
					VisitedProperty<? extends Type> property) {
				property.setValue(valueFor(idx, property.getName(), property.getType().getReturnedClass()));
				return TraversingStep.NEXT;
			}
		});
	}

	private Object valueFor(int idx, String name, Class<?> type) {
		for(ValueForStrategy strategy : valueForStrategies()) {
			if(strategy.matches(idx, name, type)){
				return strategy.valueFor(idx, name, type);
			}
		}
		return null;
	}

	private List<ValueForStrategy> valueForStrategies() {
		List<ValueForStrategy> strategies = valueForStrategies.get(this.getClass());
		if(strategies == null) {
			strategies = new ArrayList<>();
			valueForStrategies.put(getClass(), strategies);
		}
		return strategies;
	}
	
	@Override
	protected Object instantiateParameter(Method method, int parameterPosition,
			@SuppressWarnings("rawtypes") Class parameterType) {
		return valueFor(0, null, parameterType);
	}

	@Override
	protected int getTestDataSize() {
		return 2;
	}

	@Override
	protected String[] getResourcesBaseReferencia() {
		return AgileTeamCoreTestSuite.REFDB_XML;
	}

	@Override
	protected String getSpringMainXMLFilename() {
		return AgileTeamCoreTestSuite.SPRING_XML;
	}

	protected static <B> B getBean(String name, Class<B> requiredType) {
		return requiredType.cast(DBEnvironmentHolder.getInstance().getEnvironment().getHelper().getBeanFactory().getBean(name, requiredType));
	}

	/**
	 * Executa um bloco de código de forma transacionada. O banco de dados é automaticamente marcado como sujo.
	 * 
	 * @param runnable runnable
	 */
	protected void executeTransacted(Runnable runnable) {
		try {
			this.doExecuteTransacted(runnable);
		} finally {
			getEnvironment().markDirty();
		}
	}

	/**
	 * Executa um bloco de código de forma transacionada.
	 * 
	 * @param runnable runnable
	 */
	private void doExecuteTransacted(Runnable runnable) {
		boolean success = false;
		TransactionStatus status = getEnvironment().getHelper().getTransactionStatus();
		try {
			runnable.run();
			success = true;
		} finally {
			if (success) {
				getEnvironment().getHelper().commit(status);
			} else {
				getEnvironment().getHelper().rollback(status);
			}
		}
	}

	private interface ValueForStrategy {
		boolean matches(int idx, String name, Class<?> type);

		Object valueFor(int idx, String name, Class<?> type);
	}

	private class ValueForString implements ValueForStrategy {
		@Override
		public boolean matches(int idx, String name, Class<?> type) {
			return String.class.equals(type);
		}
		@Override
		public Object valueFor(int idx, String name, Class<?> type) {
			return name + idx;
		}
	}

	private class ValueForLong implements ValueForStrategy {
		@Override
		public boolean matches(int idx, String name, Class<?> type) {
			return Long.class.equals(type) || long.class.equals(type);
		}
		@Override
		public Object valueFor(int idx, String name, Class<?> type) {
			return (long)(name == null ? 0 : name.hashCode() + idx);
		}
	}

	private class ValueForInteger implements ValueForStrategy {
		@Override
		public boolean matches(int idx, String name, Class<?> type) {
			return Integer.class.equals(type) || int.class.equals(type);
		}
		@Override
		public Object valueFor(int idx, String name, Class<?> type) {
			return (int)(name == null ? 0 : name.hashCode()  & 0x0000FFFF) + idx;
		}
	}

	private class ValueForDate implements ValueForStrategy {
		@Override
		public boolean matches(int idx, String name, Class<?> type) {
			return Date.class.equals(type);
		}
		@Override
		public Object valueFor(int idx, String name, Class<?> type) {
			return new Date().addDays(idx);
		}
	}

	private class ValueForEntity implements ValueForStrategy {
		@Override
		public boolean matches(int idx, String name, Class<?> type) {
			if(Persistable.class.isAssignableFrom(type)) {
				try {
					Class<?> testClass = ReflectionUtils.findClass(type.getName() + "Test");
					return BOBaseTestCase.class.isAssignableFrom(testClass);
				} catch (Exception e) {
					return false;
				}
			}
			return false;
		}
		@Override
		public Object valueFor(int idx, String name, Class<?> type) {
			try {
				Class<?> testClass = ReflectionUtils.findClass(type.getName() + "Test");
				BOBaseTestCase<?> testCase = (BOBaseTestCase<?>) testClass.getConstructor(new Class[]{}).newInstance();
				return testCase.getSavedTestData().get(idx);
			} catch (Exception e) {
				e.printStackTrace();
				fail(e.getMessage());
				return null;
			}
		}
	}
}
